#[post("/library/bt_file_upload", data = "<data>")]
fn bt_file_upload(cookies: Cookies, data: Data, state: State<MySqlDriver>) -> String {
    let user_info: UserInfo = get_cookies(&cookies);
    let user_id: usize = user_info.id;
    let is_super: bool = is_superadmin(user_id, &state);
    if is_super == false {
        return "fail:您不是超级管理员，不能操作选题".to_string();
    }

    let mut file_hostr: HoString = HoString::new();
    file_hostr.push_bytes(data.peek());
    let file_info: Vec<HoString> = file_hostr.split_bytes("\n".as_bytes());
    let file_name_op: Option<&HoString> = file_info.get(7);
    let mut file_name: HoString = HoString::new();
    if let Some(f) = file_name_op {
        let file_name_u8: &[u8] = f.trim();
        file_name.push_bytes(file_name_u8);
    } else {
        return "fail:Data数据不合法".to_string();
    }
    let file_name_array: Vec<HoString> = file_name.split_bytes(".".as_bytes());
    let file_name_array_len: usize = file_name_array.len() - 1;
    let file_extra_name: &HoString = file_name_array.get(file_name_array_len).unwrap();
    let now: SystemTime = SystemTime::now();
    let since_epoch_res = now.duration_since(UNIX_EPOCH);
    let since_epoch: Duration;
    if let Ok(d) = since_epoch_res {
        since_epoch = d;
    } else {
        return "fail:时间戳获取失败".to_string();
    }
    let timestamp = since_epoch.as_millis();
    let upload_name: String = format!("uploads/{}.{}", timestamp, file_extra_name.to_str());
    if let Ok(_) = data.stream_to_file(upload_name.as_str()) {
        let u8_vec: Vec<u8> = HoFile::read_file_u8vec(upload_name.as_str());
        let mut bytes: HoBytes = HoBytes::new();
        bytes.push_bytes(u8_vec.as_slice());
        let u8_vec_vec: Vec<Vec<u8>> = bytes.split_byte('\n' as u8);
        let mut content: HoBytes = HoBytes::new();
        for i in 24..u8_vec_vec.len() - 1 {
            match u8_vec_vec.get(i) {
                Some(v) => content.push_bytes(v.as_slice()),
                None => break
            }
        }
        HoFile::write_file_bytes(upload_name.as_str(), content.to_bytes());
        return format!("success:{}.{}", timestamp, file_extra_name.to_str());
    } else {
        return "fail:文件移动失败".to_string();
    }
}

#[derive(FromForm)]
struct TopicAdd {
    movie_name: String,
    movie_year: u16,
    movie_link: String,
    file_list: String,
    type_str: String
}

#[post("/library/upload_topic", data = "<topic>")]
fn upload_topic(topic: Form<TopicAdd>, cookies: Cookies, state: State<MySqlDriver>) -> String {
    let user_info: UserInfo = get_cookies(&cookies);
    let user_id: usize = user_info.id;
    let is_super: bool = is_superadmin(user_id, &state);
    if is_super == false {
        return "fail:您不是超级管理员，不能操作选题".to_string();
    }

    let mut except_vec: Vec<char> = Vec::new();
    except_vec.push('\'');
    except_vec.push('"');
    except_vec.push('\\');
    let movie_name_hostr: HoString = HoString::copy_string(topic.movie_name.clone());
    let movie_link_hostr: HoString = HoString::copy_string(topic.movie_link.clone());
    let file_list_hostr: HoString = HoString::copy_string(topic.file_list.clone());
    let type_str_hostr: HoString = HoString::copy_string(topic.type_str.clone());
    if movie_name_hostr.length() == 0 || movie_link_hostr.length() == 0 {
        return "fail:表单不能有空".to_string();
    }
    if movie_name_hostr.contains_char_vec(except_vec.clone(), 0) == true ||
        movie_link_hostr.contains_char_vec(except_vec.clone(), 0) == true ||
        file_list_hostr.contains_char_vec(except_vec.clone(), 0) == true ||
        type_str_hostr.contains_char_vec(except_vec.clone(), 0) == true {
        return "fail:表单值不能包含引号和斜杠".to_string();
    }

    let pool_result: Result<Pool, mysql::Error> = Pool::new(state.driver.as_str());
    let pool: Pool;
    if let Ok(p) = pool_result {
        pool = p;
    } else {
        return "fail:无法建立数据库连接".to_string();
    }
    let conn_result: Result<PooledConn, mysql::Error> = pool.get_conn();
    let mut conn: PooledConn;
    if let Ok(c) = conn_result {
        conn = c;
    } else {
        return "fail:无法获取数据库连接".to_string();
    }

    let type_list: Vec<HoString> = type_str_hostr.split_str(",");
    let mut type_like: HoString = HoString::new();
    for item in type_list {
        if item.length() == 0 {
            continue;
        } else {
            if type_like.length() == 0 {
                type_like.push_str(format!("`type` LIKE '%,{},%' ", item.to_str()).as_str());
            } else {
                type_like.push_str(format!("OR `type` LIKE '%,{},%' ", item.to_str()).as_str());
            }
        }
    }
    let mut sql: String = format!("SELECT COUNT(`id`) AS `count` FROM `topic` WHERE `movie_name` = '{}' AND `movie_year` = {} AND ({})", movie_name_hostr.to_str(), topic.movie_year, type_like.to_str());
    let row: Row = conn.query_first(sql.as_str()).unwrap().unwrap();
    let count: usize = from_row(row);
    if count > 0 {
        return "fail:选题重复".to_string();
    }

    let file_array: Vec<HoString> = file_list_hostr.split_str("|");

    let now: SystemTime = SystemTime::now();
    let since_epoch_res = now.duration_since(UNIX_EPOCH);
    let since_epoch: Duration;
    if let Ok(d) = since_epoch_res {
        since_epoch = d;
    } else {
        return "fail:时间戳获取失败".to_string();
    }
    let timestamp = since_epoch.as_millis();
    let secs = since_epoch.as_secs();
    let tar_file: File;
    let gz_file: String = format!("{}.tar", timestamp);
    if let Ok(f) = File::create(format!("uploads/{}.tar", timestamp).as_str()) {
        tar_file = f;
    } else {
        return "fail:压缩包创建失败".to_string();
    }
    let mut tar_new = Builder::new(tar_file);
    for file_item in file_array {
        let mut open_item: File;
        if let Ok(f) = File::open(format!("uploads/{}", file_item.to_str()).as_str()) {
            open_item = f;
        } else {
            return format!("fail:{}打开失败", file_item.to_str());
        }
        if let Err(_) = tar_new.append_file(format!("{}/{}", timestamp, file_item.to_str()).as_str(), &mut open_item) {
            return format!("fail:{}压缩失败", file_item.to_str());
        }
    }

    let files_string: String = format!("{}|{}", gz_file.as_str(), file_list_hostr.to_str());
    sql = "INSERT INTO `topic`(`movie_name`, `bt_file`, `movie_year`, `type`, `movie_link`) VALUES(:movie_name, :zip_name, :year, :type_str, :movie_link)".to_string();
    if let Err(_) = conn.exec_drop(sql.as_str(), params![
        "movie_name" => movie_name_hostr.to_str(),
        "zip_name" => files_string.as_str(),
        "year" => topic.movie_year,
        "type_str" => format!(",{},", topic.type_str.as_str()).as_str(),
        "movie_link" => movie_link_hostr.to_str()
    ]) {
        return "fail:选题添加失败".to_string();
    }
    let last_id: u64 = conn.last_insert_id();
    sql = "SELECT `id` FROM `tag_group` ORDER BY `id` ASC".to_string();
    let rows: Vec<Row> = conn.query(sql.as_str()).unwrap();
    for row in rows {
        let g_id: usize = from_row(row);
        sql = "INSERT INTO `group_topic`(`group_id`, `topic_id`, `status`, `update_datetime`) VALUES(:gid, :last_id, 0, :time)".to_string();
        conn.exec_drop(sql.as_str(), params![
            "gid" => g_id,
            "last_id" => last_id,
            "time" => secs
        ]).unwrap();
    }
    return "success:".to_string();
}

#[derive(FromForm)]
struct TopicUpdate {
    id: usize,
    movie_name: String,
    movie_year: u16,
    movie_link: String,
    zip_file: String,
    file_list: String,
    remove_file_list: String,
    type_str: String
}

#[post("/library/update_topic", data = "<topic>")]
fn update_topic(topic: Form<TopicUpdate>, cookies: Cookies, state: State<MySqlDriver>) -> String {
    let user_info: UserInfo = get_cookies(&cookies);
    let user_id: usize = user_info.id;
    let is_super: bool = is_superadmin(user_id, &state);
    if is_super == false {
        return "fail:您不是超级管理员，不能操作选题".to_string();
    }

    let mut except_vec: Vec<char> = Vec::new();
    except_vec.push('\'');
    except_vec.push('"');
    except_vec.push('\\');
    let movie_name_hostr: HoString = HoString::copy_string(topic.movie_name.clone());
    let movie_link_hostr: HoString = HoString::copy_string(topic.movie_link.clone());
    let file_list_hostr: HoString = HoString::copy_string(topic.file_list.clone());
    let type_str_hostr: HoString = HoString::copy_string(topic.type_str.clone());
    if movie_name_hostr.length() == 0 || movie_link_hostr.length() == 0 {
        return "fail:表单不能有空".to_string();
    }
    if movie_name_hostr.contains_char_vec(except_vec.clone(), 0) == true ||
        movie_link_hostr.contains_char_vec(except_vec.clone(), 0) == true ||
        file_list_hostr.contains_char_vec(except_vec.clone(), 0) == true ||
        type_str_hostr.contains_char_vec(except_vec.clone(), 0) == true {
        return "fail:表单值不能包含引号和斜杠".to_string();
    }

    let pool_result: Result<Pool, mysql::Error> = Pool::new(state.driver.as_str());
    let pool: Pool;
    if let Ok(p) = pool_result {
        pool = p;
    } else {
        return "fail:无法建立数据库连接".to_string();
    }
    let conn_result: Result<PooledConn, mysql::Error> = pool.get_conn();
    let mut conn: PooledConn;
    if let Ok(c) = conn_result {
        conn = c;
    } else {
        return "fail:无法获取数据库连接".to_string();
    }

    let type_list: Vec<HoString> = type_str_hostr.split_bytes(",".as_bytes());
    let mut type_like: HoString = HoString::new();
    for item in type_list {
        if item.length() == 0 {
            continue;
        } else {
            if type_like.length() == 0 {
                type_like.push_str(format!("`type` LIKE '%,{},%' ", item.to_str()).as_str());
            } else {
                type_like.push_str(format!("OR `type` LIKE '%,{},%' ", item.to_str()).as_str());
            }
        }
    }
    let mut sql: String = format!("SELECT COUNT(`id`) AS `count` FROM `topic` WHERE `movie_name` = '{}' AND `movie_year` = {} AND ({}) AND `id` <> {}", movie_name_hostr.to_str(), topic.movie_year, type_like.to_str(), topic.id);
    let row: Row = conn.query_first(sql.as_str()).unwrap().unwrap();
    let count: usize = from_row(row);
    if count > 0 {
        return "fail:选题重复".to_string();
    }

    let file_array: Vec<HoString> = file_list_hostr.split_str("|");

    let now: SystemTime = SystemTime::now();
    let since_epoch_res = now.duration_since(UNIX_EPOCH);
    let since_epoch: Duration;
    if let Ok(d) = since_epoch_res {
        since_epoch = d;
    } else {
        return "fail:时间戳获取失败".to_string();
    }
    let timestamp = since_epoch.as_millis();
    let gz_file: String = format!("{}.tar", timestamp);
    let mut files_string: String = String::new();
    let tar_file: File;
    if let Ok(f) = File::create(format!("uploads/{}", gz_file.as_str()).as_str()) {
        tar_file = f;
    } else {
        return "fail:压缩包创建失败".to_string();
    }
    let mut tar_new = Builder::new(tar_file);
    let remove_file_list: HoString = HoString::copy_string(topic.remove_file_list.clone());
    let remove_file_vec: Vec<HoString> = remove_file_list.split_str("|");
    let mut is_remove: bool = false;
    for file_item in file_array {
        for remove_file_item in &remove_file_vec {
            if remove_file_item.compare_str(file_item.to_str()) {
                is_remove = true;
                break;
            }
        }
        if is_remove {
            is_remove = false;
            continue;
        }
        let mut open_item: File;
        if let Ok(f) = File::open(format!("uploads/{}", file_item.to_str()).as_str()) {
            open_item = f;
        } else {
            return format!("fail:{}打开失败", file_item.to_str());
        }
        if let Err(_) = tar_new.append_file(format!("{}/{}", timestamp, file_item.to_str()).as_str(), &mut open_item) {
            return format!("fail:{}压缩失败", file_item.to_str());
        }
        if files_string.len() == 0 {
            files_string.push_str(file_item.to_str());
        } else {
            files_string.push_str(format!("|{}", file_item.to_str()).as_str());
        }
    }

    sql = format!("UPDATE `topic` SET `movie_name` = :movie_name, `movie_year` = :year, `bt_file` = :zip_name, `type` = :type_str, `movie_link` = :movie_link WHERE `id` = :id");
    let _ = conn.exec_drop(sql.as_str(), params![
        "movie_name" => movie_name_hostr.to_str(),
        "year" => topic.movie_year,
        "zip_name" => format!("{}|{}", gz_file.as_str(), files_string.as_str()).as_str(),
        "type_str" => topic.type_str.as_str(),
        "movie_link" => topic.movie_link.as_str(),
        "id" => topic.id
    ]);

    for file in remove_file_vec {
        let _ = remove_file(format!("uploads/{}", file.to_str()).as_str());
    }
    let _ = remove_file(format!("uploads/{}", topic.zip_file.as_str()).as_str());
    
    return "success:".to_string();
}

#[get("/add_topic")]
fn add_topic(cookies: Cookies, state: State<MySqlDriver>) -> Response<'static> {
    let mut res = Response::new();
    res.set_header(ContentType::HTML);

    // mod_id: String, 
    // mod_menu_open: String, 
    // sub_mod_id: String, 
    // page_title: String, 
    // title: String
    let header: String = get_header(
        "topic-mod".to_string(), 
        "topic-mod-menu-open".to_string(), 
        String::new(),
        "用户后台 - 添加选题".to_string(), 
        "添加选题".to_string(), &cookies,
        &state);

    let mut src_vec: Vec<&[u8]> = Vec::new();
    let mut replace_vec: Vec<&[u8]> = Vec::new();

    let footer_string: String = get_footer();
    src_vec.push("{header}".as_bytes());
    src_vec.push("{base_url}".as_bytes());
    src_vec.push("{footer}".as_bytes());
    replace_vec.push(header.as_bytes());
    replace_vec.push(state.base_url.as_bytes());
    replace_vec.push(footer_string.as_bytes());

    let mut page_str: HoString = HoFile::read_file_bytes("web/add_topic.html");
    page_str = page_str.replace_bytes_vec(src_vec, replace_vec);
    let html: String = page_str.to_str().to_string().to_owned();
    res.set_sized_body(Cursor::new(html));
    return res;
}

#[get("/edit_topic?<id>")]
fn edit_topic(id: usize, cookies: Cookies, state: State<MySqlDriver>) -> Response<'static> {
    let mut res = Response::new();
    res.set_header(ContentType::HTML);

    // mod_id: String, 
    // mod_menu_open: String, 
    // sub_mod_id: String, 
    // page_title: String, 
    // title: String
    let header: String = get_header(
        "topic-mod".to_string(), 
        "topic-mod-menu-open".to_string(), 
        String::new(),
        "用户后台 - 编辑选题".to_string(), 
        "编辑选题".to_string(), &cookies,
        &state);

    let pool_result: Result<Pool, mysql::Error> = Pool::new(state.driver.as_str());
    let pool: Pool;
    if let Ok(p) = pool_result {
        pool = p;
    } else {
        res.set_sized_body(Cursor::new("无法建立数据库连接".to_string()));
        return res;
    }
    let conn_result: Result<PooledConn, mysql::Error> = pool.get_conn();
    let mut conn: PooledConn;
    if let Ok(c) = conn_result {
        conn = c;
    } else {
        res.set_sized_body(Cursor::new("无法获取数据库连接".to_string()));
        return res;
    }

    let sql: String = format!("SELECT `movie_name`, `bt_file`, `movie_year`, `type` AS `topic_type`, `movie_link` FROM `topic` WHERE `id` = {} LIMIT 1", id);
    let row_op: Option<Row> = conn.query_first(sql.as_str()).unwrap();
    let row: Row;
    if let Some(r) = row_op {
        row = r;
    } else {
        res.set_sized_body(Cursor::new("找不到该选题".to_string()));
        return res;
    }
    let (movie_name, bt_file, movie_year, topic_type, movie_link): (String, String, u16, String, String) = from_row(row);
    let topic_type_hostr: HoString = HoString::copy_string(topic_type);
    let topic_types: Vec<HoString> = topic_type_hostr.split_bytes(",".as_bytes());
    let mut topic_type_html: String = String::new();
    let type_max: usize;
    let mut type_list: String = String::new();
    let mut type_list_i: usize = 0;
    for topic_item in topic_types {
        if topic_item.length() != 0 {
            topic_type_html.push_str(format!("<div class=\"input-group\" id=\"type{}\">\
                                          <input type=\"text\" class=\"form-control\" id=\"input{}\" value=\"{}\">\
                                          <div class=\"input-group-append\">\
                                            <button class=\"btn btn-outline-secondary\" type=\"button\" onclick=\"remove_type('type{}')\">-</button>\
                                          </div>\
                                    </div>", type_list_i, type_list_i, topic_item.to_str(), type_list_i).as_str());
            type_list.push_str(format!("type_list.push({});", type_list_i).as_str());
            type_list_i += 1;
        }
    }
    type_max = type_list_i;

    let bt_file_hostr: HoString = HoString::copy_string(bt_file);
    let file_list: Vec<HoString> = bt_file_hostr.split_str("|");
    let mut file_list_except_first_str: String = String::new();
    let mut file_index: usize = 0;
    let mut upload_list: String = String::new();
    let mut first_tar: String = String::new();
    for file_item in file_list {
        if file_index == 0 {
            first_tar = file_item.to_string();
        } else {
            file_list_except_first_str.push_str(format!("file_list.push('{}');", file_item.to_str()).as_str());
            upload_list.push_str(format!("<li class=\"list-group-item d-flex justify-content-between align-items-center\" id=\"file{}\">\
                                              <a target=\"_blank\" href=\"{}uploads/bt_file/{}\" download=\"{}\">{}</a>\
                                              <span class=\"badge badge-success badge-pill\">已上传</span>\
                                              <button type=\"button\" class=\"badge badge-danger badge-pill\" onclick=\"remove_file(0, '{}', 'file{}')\">删除</button>\
                                        </li>", file_index - 1, state.base_url.as_str(), file_item.to_str(), file_item.to_str(), file_item.to_str(), file_item.to_str(), file_index - 1).as_str());
        }
        file_index += 1;
    }

    let mut src_vec: Vec<&[u8]> = Vec::new();
    let mut replace_vec: Vec<&[u8]> = Vec::new();

    let movie_year_str: String = movie_year.to_string();
    let type_max_str: String = type_max.to_string();
    let id_str: String = id.to_string();
    let footer_string: String = get_footer();
    src_vec.push("{header}".as_bytes());
    src_vec.push("{movie_name}".as_bytes());
    src_vec.push("{movie_year}".as_bytes());
    src_vec.push("{topic_type}".as_bytes());
    src_vec.push("{type_list}".as_bytes());
    src_vec.push("{max}".as_bytes());
    src_vec.push("{movie_link}".as_bytes());
    src_vec.push("{first_tar}".as_bytes());
    src_vec.push("{upload_list}".as_bytes());
    src_vec.push("{base_url}".as_bytes());
    src_vec.push("{id}".as_bytes());
    src_vec.push("{file_list_except_first_str}".as_bytes());
    src_vec.push("{footer}".as_bytes());
    replace_vec.push(header.as_bytes());
    replace_vec.push(movie_name.as_bytes());
    replace_vec.push(movie_year_str.as_bytes());
    replace_vec.push(topic_type_html.as_bytes());
    replace_vec.push(type_list.as_bytes());
    replace_vec.push(type_max_str.as_bytes());
    replace_vec.push(movie_link.as_bytes());
    replace_vec.push(first_tar.as_bytes());
    replace_vec.push(upload_list.as_bytes());
    replace_vec.push(state.base_url.as_bytes());
    replace_vec.push(id_str.as_bytes());
    replace_vec.push(file_list_except_first_str.as_bytes());
    replace_vec.push(footer_string.as_bytes());

    let mut page_str: HoString = HoFile::read_file_bytes("web/edit_topic.html");
    page_str = page_str.replace_bytes_vec(src_vec, replace_vec);
    let html: String = page_str.to_str().to_string().to_owned();
    res.set_sized_body(Cursor::new(html));
    return res;
}

